package com.sun.filesigndemo.common.redis;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.filesigndemo.common.conf.Global;
import com.sun.filesigndemo.common.utils.ObjectUtils;
import com.sun.filesigndemo.common.utils.StringUtils;
import org.apache.commons.lang3.SerializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@SuppressWarnings({"unchecked", "rawtypes", "unused"})
public class CacheServiceImpl implements CacheService {
	
	private final static String KEY_PREFIX = Global.get("projectName") + ":";
	
	private volatile RedisTemplate<String, Object> redisTemplate;
	
	@Autowired(required = false)
	public void setRedisTemplate(RedisTemplate redisTemplate) {
		
	    RedisSerializer stringSerializer = new RedisSerializer<Object>() {
			@Override
			public byte[] serialize(Object obj) {
				try {
					return (obj == null ? null : obj.toString().getBytes("UTF8"));
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return null;
			}

			@Override
			public String deserialize(byte[] bytes) {
				try {
					return (bytes == null ? null : new String(bytes, "UTF8"));
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return null;
			}
		};
	    redisTemplate.setKeySerializer(stringSerializer);
		// 使用Jackson2JsonRedisSerialize 替换默认序列化
//		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//
//		ObjectMapper objectMapper = new ObjectMapper();
//		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//
//		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//
//		// 设置value的序列化规则和 key的序列化规则
//		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//	    redisTemplate.setValueSerializer(stringSerializer);
	    redisTemplate.setHashKeySerializer(stringSerializer);
	    this.redisTemplate = redisTemplate;
	}
	
    private static final Logger log = LoggerFactory.getLogger(CacheServiceImpl.class);

	public boolean set(final String key, final Object value) {
		String newKey = KEY_PREFIX + key;
		redisTemplate.boundValueOps(newKey).set(value);
		return true ;
	}
	
	
	public boolean set(final byte[] key, final byte[] value) {
		byte[] newKey = this.getByteKey(key);
		redisTemplate.execute(new RedisCallback<Long>() {
	        public Long doInRedis(RedisConnection connection) throws DataAccessException {
	        	connection.set(newKey, value);
	            return 1l;
	        }
	    });
		return true ;
	}
	
	
	private byte[] getByteKey(byte[] key) {
		byte[] prefixByte = KEY_PREFIX.getBytes();
		byte[] newKey = new byte[prefixByte.length + key.length];
		System.arraycopy(prefixByte, 0, newKey, 0, prefixByte.length);
		System.arraycopy(key, 0, newKey, prefixByte.length, key.length);
		return newKey;
	}
	
	
	public boolean set(final String key, final Object value, final Long exp) {
		String newKey = KEY_PREFIX + key;
		redisTemplate.boundValueOps(newKey).set(value, exp, TimeUnit.SECONDS);
		return true ;
	}
	
	public boolean hasKey(String key ) {
		key = KEY_PREFIX + key;
		return redisTemplate.hasKey(key);
	}

	public boolean putHash(String key, Object fieldKey, Object fieldValue) {
		key = KEY_PREFIX + key;
		if(fieldValue == null){
			return false;
		}
		redisTemplate.boundHashOps(key).put(fieldKey, fieldValue);
		return true ;
	}
	
	public <T> T getHash(String key, Object fieldKey) {
		key = KEY_PREFIX + key;
		return (T) redisTemplate.boundHashOps(key).get(fieldKey);
	}
	
	public Long getHashSize(String key) {
		key = KEY_PREFIX + key;
		return redisTemplate.boundHashOps(key).size();
	}
	
	public Set<Object> getHashKeys(String key) {
		key = KEY_PREFIX + key;
		return redisTemplate.boundHashOps(key).keys();
	}
	
	public List<Object> getHashValues(String key) {
		key = KEY_PREFIX + key;
		return redisTemplate.boundHashOps(key).values();
	}

    public boolean hasHashKey(String key,Object fieldKey) {
    	key = KEY_PREFIX + key;
        return redisTemplate.boundHashOps(key).hasKey(fieldKey);
    }
	
	public Map<String, Object> getHash(String key){
		key = KEY_PREFIX + key;
		BoundHashOperations<String, String, Object> boundHashOps = redisTemplate.boundHashOps(key);
		if(boundHashOps != null){
			return boundHashOps.entries();
		}
		return null;
	}
	
	public boolean deleteHash(String key, Object fieldKey) {
		key = KEY_PREFIX + key;
		redisTemplate.boundHashOps(key).delete(fieldKey);
		return true;
	}

    @Override
    public boolean deleteHash(String key) {
    		key = KEY_PREFIX + key;
        redisTemplate.delete(key);
        return true;
    }

	public <T> T get(String key) {
		key = KEY_PREFIX + key;
		return (T) redisTemplate.boundValueOps(key).get();
	}
	
	public byte[] getByte(final byte[] key){
		byte[] newKey = this.getByteKey(key);
		return redisTemplate.execute(new RedisCallback<byte[]>() {
            public byte[] doInRedis(RedisConnection connection){
            	return connection.get(newKey);
            }
        });
	}

	public boolean delete(String key) {
		key = KEY_PREFIX + key;
		redisTemplate.delete(key);
		return true;
	}

	public boolean deleteAbstract(String key) {
		key = KEY_PREFIX + key;
		redisTemplate.delete(redisTemplate.keys(key));
		return true;
	}
	
	private RedisSerializer<String> getRedisSerializer() {
		
	    return redisTemplate.getStringSerializer();
	}

	private RedisSerializer<Object> getRedisObjectSerializer() {
		return new RedisSerializer<Object>() {
			
			@Override
			public byte[] serialize(Object t) throws SerializationException {
				ByteArrayOutputStream bStream = new ByteArrayOutputStream();
				try {
					ObjectOutputStream oStream = new ObjectOutputStream(bStream);
					oStream.writeObject(t);
				} catch (IOException e) {
					log.warn(e.getMessage(),e);
				}
				return bStream.toByteArray();
			}

			@Override
			public Object deserialize(byte[] bytes)
					throws SerializationException {
				if(bytes == null) {
					return null;
				}
				ByteArrayInputStream bStream = new ByteArrayInputStream(bytes);
				try {
					ObjectInputStream oStream = new ObjectInputStream(bStream);
					Object object = oStream.readObject();
					return object;
				} catch (IOException e) {
					log.warn(e.getMessage(),e);
				} catch (ClassNotFoundException e) {
					log.warn(e.getMessage(),e);
				}
				return null;
			}
		};
	}
	
	public boolean setSerializer(final String key, final Object value, final Long exp){
		String newKey = KEY_PREFIX + key;
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(value.getClass()));
		//redisTemplate.boundValueOps(key).set(value, exp, TimeUnit.SECONDS);
		ValueOperations<String, Object> operations = redisTemplate.opsForValue(); 
		ObjectMapper objectMapper = new ObjectMapper();
		
		try {
			operations.set(newKey, objectMapper.writeValueAsString(value), exp, TimeUnit.SECONDS);
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true ;
	}

	public boolean setSerializer(String key, Object value)
	{
		key = KEY_PREFIX + key;
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(value.getClass()));
		//redisTemplate.boundValueOps(key).set(value);
		ValueOperations<String, Object> operations = redisTemplate.opsForValue(); 
		ObjectMapper objectMapper = new ObjectMapper();
		
		try {
			operations.set(key, objectMapper.writeValueAsString(value));
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true ;
	}

	public <T> T getSerializer(String key, Class<?> clazz) 
	{
		key = KEY_PREFIX + key;
		redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(clazz));
		//Object res = redisTemplate.boundValueOps(key).get();
		ValueOperations<String, Object> operations = redisTemplate.opsForValue(); 
		String json = (String) operations.get(key);
		if(json == null)
		{
			return null;
		}
		ObjectMapper objectMapper = new ObjectMapper();
	    // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性  
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		try 
		{
			return (T) objectMapper.readValue(json, clazz);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		return null;
	}
	
	public boolean addSetMember(String key, Object... members){
		key = KEY_PREFIX + key;
		redisTemplate.boundSetOps(key).add(members);
		return true;
	}
	
	public boolean hasSetMember(String key, Object member){
		key = KEY_PREFIX + key;
		return redisTemplate.boundSetOps(key).isMember(member);
	}

	@Override
	public Set<String> getKeys(String pattern) {
		return redisTemplate.keys(pattern);
	}

	@Override
	public Set<String> getHashTableFields(final String key) {
		String newKey = KEY_PREFIX + key;
		return  redisTemplate.execute(new RedisCallback<Set<String>>() {

			@Override
			public Set<String> doInRedis(RedisConnection connection)
					throws DataAccessException {
				Set<byte[]> fields = null;
				try {
					fields = connection.hKeys(newKey.getBytes("UTF-8"));
				} catch (UnsupportedEncodingException e1) {
					e1.printStackTrace();
				}
				Set<String> fieldSet = new HashSet<String>();
				if(fields!=null){
					Iterator<byte[]> iter = fields.iterator();
					byte[] field ;
					while(iter.hasNext()){
						field = iter.next();
						try {
							fieldSet.add(new String(field,"UTF-8"));
						} catch (UnsupportedEncodingException e) {
							e.printStackTrace();
						}
					}
				}
				
				return fieldSet;
			}
			
		});
	}
	
	@Override
	public boolean setExpire(String key, long timeout) {
		key = KEY_PREFIX + key;
		return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
	}
	
	public long getExpire(String key) {
		key = KEY_PREFIX + key;
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}
	
	@Override
	public boolean deleteByPattern(String pattern) {
		Set<String> keys = redisTemplate.keys(pattern);
		if (keys!=null&&keys.size() > 0){
			redisTemplate.delete(keys);
		}
		return true;
	}
	
	@Override
	public boolean addZSetMember(String key,Object value,double score) {
		key = KEY_PREFIX + key;
		ObjectMapper objectMapper = new ObjectMapper();
		try {
			redisTemplate.boundZSetOps(key).add(objectMapper.writeValueAsString(value), score);
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;
	}
	
	@Override
	public Set<TypedTuple<Object>> zSetWithScores(String key,long start,long end) {
		key = KEY_PREFIX + key;
		BoundZSetOperations<String, Object> zo =  redisTemplate.boundZSetOps(key);
		Set<TypedTuple<Object>> set = zo.rangeWithScores(start, end);
		return set;
	}
	@Override
	public Set<TypedTuple<Object>> zSetReverseRangeWithScores(String key,long start,long end) {
		key = KEY_PREFIX + key;
		BoundZSetOperations<String, Object> zo =  redisTemplate.boundZSetOps(key);
		Set<TypedTuple<Object>> set = zo.reverseRangeWithScores(start, end);
		return set;
	}
	
	@Override
	public Object zSetscore(String key,String value) {
		key = KEY_PREFIX + key;
		BoundZSetOperations<String, Object> zo =  redisTemplate.boundZSetOps(key);
		return zo.score(value);
	}
	/**
	 * 获取缓存大小
	 * @return
	 */
	@Override
	public long dbSize() {
        return redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
    }

	public boolean setNX(String key, Long value) {
		key = KEY_PREFIX + key;
		return redisTemplate.opsForValue().setIfAbsent(key, String.valueOf(value));
	}

	public Object getLock(String key) {
		key = KEY_PREFIX + key;
		return redisTemplate.opsForValue().get(key);
	}

	public Long getSet(String key, Long value) {
		key = KEY_PREFIX + key;
		return Long.valueOf(redisTemplate.opsForValue().getAndSet(key, String.valueOf(value)).toString());
	}
	
	/**
	 * 获取byte[]类型Key
	 * @return
	 */
	public byte[] toBytes(Object object) {
		if(object instanceof String){
	    		return StringUtils.getBytes((String)object);
	    	}else{
	    		return ObjectUtils.serialize(object);
	    	}
	}
	
	/**
	 * byte[]型转换Object
	 * @return
	 */
	public Object toObject(byte[] bytes) {
		return ObjectUtils.unserialize(bytes);
	}


	public Boolean setBit(String key, long offset, boolean value) {
		key = KEY_PREFIX + key;
		redisTemplate.opsForValue().setBit(key, offset, value);
		return true;
	}

	public BitSet getBit(String key) {
		final String _key = KEY_PREFIX + key;
		return redisTemplate.execute(new RedisCallback<BitSet>() {
			public BitSet doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] bytes=connection.get(_key.getBytes());
				if (bytes == null) {
					return null;
				}
				BitSet bitSet= BitSet.valueOf(bytes);
				return bitSet;
			}
		});
	}

	/**
	 * 统计bit位为1的总数
	 * @param key
	 */
	public long bitCount(String key) {
		final String _key = KEY_PREFIX + key;
		return redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				long result = 0;
				result = connection.bitCount(_key.getBytes());
				return result;
			}
		});
	}

}
